// FlyingColors v1.1 alpha

(function (window, document) {

// Thanks, @TimDown
function getTextNodesIn(node) {
	var textNodes = [];
	if (node.nodeType == 3) {
		textNodes.push(node);
	} else {
		var children = node.childNodes;
		for (var i = 0, len = children.length; i < len; ++i) {
			textNodes.push.apply(textNodes, getTextNodesIn(children[i]));
		}
	}
	return textNodes;
}

// Thanks, @TimDown
function setSelectionRange(el, start, end) {
	var range = document.createRange();
	range.selectNodeContents(el);
	var textNodes = getTextNodesIn(el);
	var foundStart = false;
	var charCount = 0, endCharCount;
	for (var i = 0, textNode; textNode = textNodes[i++]; ) {
		endCharCount = charCount + textNode.length;
		if (!foundStart && start >= charCount
				&& (start < endCharCount ||
				(start == endCharCount && i < textNodes.length))) {
			range.setStart(textNode, start - charCount);
			foundStart = true;
		}
		if (foundStart && end <= endCharCount) {
			range.setEnd(textNode, end - charCount);
			break;
		}
		charCount = endCharCount;
	}
	var sel = window.getSelection();
	sel.removeAllRanges();
	sel.addRange(range);
}

function flyingcolors (el, spec) {
	if (typeof el === "string") {
		if (window.jQuery) el = jQuery(el).get();
		else el = document.querySelectorAll(el);
	}

	if (el.length) {
		for (var i = 0, l = el.length; i !== l; i++) {
			flyingcolors(el[i], spec);
		}

		return;
	}

	var oCEval = el.contenteditable;
	el.contenteditable = true;

	var text = el.textContent;

	for (var i in spec) {
		var begin = '<span class="' + i + '">',
			finish = '</span>';

		var re;
		if (spec[i] instanceof RegExp) re = spec[i];
		else re = new RegExp(spec[i], 'g');

		var match;
		while ((match = re.exec(text)) !== null) {
			var oMatch = match[0];

			setSelectionRange(el, match.index, match.index + oMatch.length);
			document.execCommand('insertHTML', false, begin + oMatch + finish);
		}
	}

	el.contenteditable = oCEval;
}

// Export
window.flyingcolors = flyingcolors;

// DOM plugin
try { HTMLElement.prototype.flyingcolors = function(spec) {
	return flyingcolors(this, spec);
}; } catch (e) {  }

// jQuery plugin
if (window.jQuery) {
	jQuery.fn.flyingcolors = function(spec) {
		return this.each(function() {
			flyingcolors(this, spec);
		});
	};
}

})(window, document);
